//
// Created by QU on 2024/5/6.
//

#include <iostream>
#include <fstream>
#include <sstream>
#include <functional>
#include <utility>
#include <memory>
#include <vector>
#include <map>
#include <set>

using std::cout, std::cin, std::endl, std::unitbuf;

class Query_base;

class QueryResult;

class TextQuery;

class Query_base {
    friend class Query;

protected:
    using line_no = std::string::size_type; // used in the eval functions
    virtual ~Query_base() = default;

private:
    // eval returns the QueryResult that matches this Query
    virtual QueryResult eval(const TextQuery &) const = 0;

    // rep is a string representation of the query
    virtual std::string rep() const = 0;
};

// face on users class.
class Query {
public:
    Query(const std::string &);

    QueryResult eval(const TextQuery &) const;

    std::string rep() const {
        return base->rep();
    }

private:
    friend Query operator~(const Query &);

    friend Query operator|(const Query &, const Query &);

    friend Query operator&(const Query &, const Query &);

    Query(std::shared_ptr<Query_base> queryBase) : base(queryBase) {}

    // 实际存储的是Query_base类, Query_base负责隐藏, 他里面使用虚函数, 得到继承具体实现的.
    std::shared_ptr<Query_base> base;
};


class NotQuery : public Query_base {
    friend Query operator~(const Query &);

    NotQuery(const Query &q) : query(q) {}

    // concrete class: NotQuery defines all inherited pure virtual functions
    std::string rep() const { return "~(" + query.rep() + ")"; }

    QueryResult eval(const TextQuery &) const;

    Query query;
};

class BinaryQuery : public Query_base {
protected:
    BinaryQuery(const Query &l, const Query &r, std::string s) :
            lhs(l), rhs(r), opSym(s) {}

    // abstract class: BinaryQuery doesn't define eval
    std::string rep() const {
        return "(" + lhs.rep() + " "
               + opSym + " "
               + rhs.rep() + ")";
    }

    Query lhs, rhs;    // right- and left-hand operands
    std::string opSym; // name of the operator
};

class AndQuery : public BinaryQuery {
    friend Query operator&(const Query &, const Query &);

    AndQuery(const Query &left, const Query &right) :
            BinaryQuery(left, right, "&") {}

    // concrete class: AndQuery inherits rep and defines the remaining pure virtual
    QueryResult eval(const TextQuery &) const;
};

class OrQuery : public BinaryQuery {
    friend Query operator|(const Query &, const Query &);

    OrQuery(const Query &left, const Query &right) :
            BinaryQuery(left, right, "|") {}

    QueryResult eval(const TextQuery &) const;
};

class TextQuery {
public:
    using line_no = std::vector<std::string>::size_type;

    TextQuery(std::ifstream &);

    QueryResult query(const std::string &) const;

    void display_map();        // debugging aid: print the map
private:
    std::shared_ptr<std::vector<std::string>> file; // input file
    // maps each word to the set of the lines in which that word appears
    std::map<std::string,
            std::shared_ptr<std::set<line_no>>> wm;

    // canonicalizes text: removes punctuation and makes everything lower case
    static std::string cleanup_str(const std::string &);
};

class QueryResult {
    friend std::ostream &print(std::ostream &, const QueryResult &);

public:
    typedef std::vector<std::string>::size_type line_no;
    typedef std::set<line_no>::const_iterator line_it;

    QueryResult(std::string s,
                std::shared_ptr<std::set<line_no>> p,
                std::shared_ptr<std::vector<std::string>> f) :
            sought(s), lines(p), file(f) {}

    std::set<line_no>::size_type size() const { return lines->size(); }

    line_it begin() const { return lines->cbegin(); }

    line_it end() const { return lines->cend(); }

    std::shared_ptr<std::vector<std::string>> get_file() { return file; }

private:
    std::string sought;  // word this query represents
    std::shared_ptr<std::set<line_no>> lines; // lines it's on
    std::shared_ptr<std::vector<std::string>> file;  //input file
};

std::ostream &print(std::ostream &, const QueryResult &);